(JAVA) Competitive programming을 위한 Java의 빠른 입출력

Reference

  • Fast I/O in Java in Competitive Programming - geeksforgeeks.com
  • Java IO : Input-output in Java with Examples - geeksforgeeks.com
  • 입력 속도 비교 - acmicpc.net
  • 출력 속도 비교 - acmicpc.net


Problem Solving(PS)에서 입출력은 매우 중요하다고 생각한다.
물론 어떤 자료구조와 알고리즘으로 문제를 해결할지도 매우 중요하고 핵심적인 부분이지만, 입력이 없으면 쓸모가 없고 출력이 없으면 의미가 없다. 또한 가끔 Online Judge 사이트를 통해 알고리즘 문제를 풀다 보면 입출력 속도 차이로 통과하거나 하지 못하는 경우도 있다.

그래서 JAVA의 입출력과 관련해서 한 번 정리해보려고 한다. 👍

결론부터 말하자면, BufferedReader와 StringTokenizer를 사용하자!

🎯 geeksforgeeks.com에서 Rishabh Mahrsee 님이 작성하신 글에는 4가지 방식을 제시하고 있다.

Scanner Class

😊 매우 간단하고 문자열 파싱이 가능하며 nextInt(), next()와 같이 데이터 타입에 따라 입력받기 때문에 따로 casting이 필요하지 않다

😭 비교적 느린편에 속한다. Syncronized 하지 않고 IOException을 숨기는 특징을 가지고 있다.

Working program using Scanner
import java.util.Scanner;

public class Main
{
public static void main(String[] args)
{
Scanner s = new Scanner(System.in);
int n = s.nextInt();
int k = s.nextInt();
int count = 0;
while (n-- > 0)
{
int x = s.nextInt();
if (x%k == 0)
count++;
}
System.out.println(count);

BufferedReader

😊 버퍼를 사용하기 때문에 빠르다! Scanner가 1024의 버퍼 크기를 갖는데 비해 8194의 더 큰 버퍼 크기를 가지고 Scanner는 byte-input-stream으로 받지만 BufferedReader는 character-input-stream을 사용한다.

😭 String 타입으로 모두 입력 받기 때문에 데이터를 직접 가공해서 사용해야한다.

Working program using BufferedReader
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main
{
public static void main(String[] args) throws IOException
{

BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));

StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int k = Integer.parseInt(st.nextToken());
int count = 0;
while (n-- > 0)
{
int x = Integer.parseInt(br.readLine());
if (x%k == 0)
count++;
}
System.out.println(count);
}

}

Reader Class

원글의 저자는 Reader Class로 정의해서 사용하는 방법을 가장 추천하지만 개인적으로 알고리즘 문제를 풀 때 특히 CP라면 굳이 아래 내용을 기억하고 사용하는 것을 추천하지 않는다.

😊 BufferedReader와 StringTokenizer를 하나의 클래스로 정리하여 타이핑을 줄일 수 있다.

😭 직접 사용하는 것 보다 아주 조금 느리다.

Working program with FastReader
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
import java.util.StringTokenizer;

public class Main
{
static class FastReader
{
BufferedReader br;
StringTokenizer st;

public FastReader()
{
br = new BufferedReader(new
InputStreamReader(System.in));
}

String next()
{
while (st == null || !st.hasMoreElements())
{
try
{
st = new StringTokenizer(br.readLine());
}
catch (IOException e)
{
e.printStackTrace();
}
}
return st.nextToken();
}

int nextInt()
{
return Integer.parseInt(next());
}

long nextLong()
{
return Long.parseLong(next());
}

double nextDouble()
{
return Double.parseDouble(next());
}

String nextLine()
{
String str = "";
try
{
str = br.readLine();
}
catch (IOException e)
{
e.printStackTrace();
}
return str;
}
}

public static void main(String[] args)
{
FastReader s=new FastReader();
int n = s.nextInt();
int k = s.nextInt();
int count = 0;
while (n-- > 0)
{
int x = s.nextInt();
if (x%k == 0)
count++;
}
System.out.println(count);
}

}

FastReader Class

😊 직접 버퍼 사이즈를 지정하며 DataInputStream을 사용하여 제일 빠르게 입력받는다.

😭 직접 정의하기 때문에 외울게 많다…

Working program using Reader Class
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
import java.util.StringTokenizer;

public class Main
{
static class Reader
{
final private int BUFFER_SIZE = 1 << 16;
private DataInputStream din;
private byte[] buffer;
private int bufferPointer, bytesRead;

public Reader()
{
din = new DataInputStream(System.in);
buffer = new byte[BUFFER_SIZE];
bufferPointer = bytesRead = 0;
}

public Reader(String file_name) throws IOException
{
din = new DataInputStream(new FileInputStream(file_name));
buffer = new byte[BUFFER_SIZE];
bufferPointer = bytesRead = 0;
}

public String readLine() throws IOException
{
byte[] buf = new byte[64]; // line length
int cnt = 0, c;
while ((c = read()) != -1)
{
if (c == '\n')
break;
buf[cnt++] = (byte) c;
}
return new String(buf, 0, cnt);
}

public int nextInt() throws IOException
{
int ret = 0;
byte c = read();
while (c <= ' ')
c = read();
boolean neg = (c == '-');
if (neg)
c = read();
do
{
ret = ret * 10 + c - '0';
} while ((c = read()) >= '0' && c <= '9');

if (neg)
return -ret;
return ret;
}

public long nextLong() throws IOException
{
long ret = 0;
byte c = read();
while (c <= ' ')
c = read();
boolean neg = (c == '-');
if (neg)
c = read();
do {
ret = ret * 10 + c - '0';
}
while ((c = read()) >= '0' && c <= '9');
if (neg)
return -ret;
return ret;
}

public double nextDouble() throws IOException
{
double ret = 0, div = 1;
byte c = read();
while (c <= ' ')
c = read();
boolean neg = (c == '-');
if (neg)
c = read();

do {
ret = ret * 10 + c - '0';
}
while ((c = read()) >= '0' && c <= '9');

if (c == '.')
{
while ((c = read()) >= '0' && c <= '9')
{
ret += (c - '0') / (div *= 10);
}
}

if (neg)
return -ret;
return ret;
}

private void fillBuffer() throws IOException
{
bytesRead = din.read(buffer, bufferPointer = 0, BUFFER_SIZE);
if (bytesRead == -1)
buffer[0] = -1;
}

private byte read() throws IOException
{
if (bufferPointer == bytesRead)
fillBuffer();
return buffer[bufferPointer++];
}

public void close() throws IOException
{
if (din == null)
return;
din.close();
}
}

public static void main(String[] args) throws IOException
{
Reader s=new Reader();
int n = s.nextInt();
int k = s.nextInt();
int count=0;
while (n-- > 0)
{
int x = s.nextInt();
if (x%k == 0)
count++;
}
System.out.println(count);
}

}